#include "ice/ice_connection.h"
#include "ice/udp_port.h"
#include <rtc_base/logging.h>
#include <rtc_base/byte_buffer.h>
#include <rtc_base/socket_address.h>
#include <rtc_base/time_utils.h>
#include <rtc_base/helpers.h>
namespace xrtc
{

    const int RTT_RATIO = 3; // old_rtt:new_rtt= 3:1
    const int MAX_RTT = 60000;
    const int MIN_RTT = 100;
    // ConnectionRequest
    ConnectionRequest::ConnectionRequest(IceConnection *conn) : StunRequest(new StunMessage()), _connection(conn)
    {
    }
    void ConnectionRequest::Prepare(StunMessage *msg)
    {

        msg->SetType(STUN_BINDING_REQUEST);
        std::string username;
        _connection->Port()->CreateStunUsername(_connection->RemoteCandidate().username, &username);
        // add new attr
        msg->AddAttribute(std::make_unique<StunByteStringAttribute>(STUN_ATTR_USERNAME, username));
        msg->AddAttribute(std::make_unique<StunUInt64Attribute>(STUN_ATTR_ICE_CONTROLLING, 0));
        // 积极提名
        msg->AddAttribute(std::make_unique<StunByteStringAttribute>(STUN_ATTR_USE_CANDIDATE, 0));
        // priority
        int type_pref = ICE_TYPE_PREFERENCE_PRFLX;
        uint32_t prflx_priority = (type_pref << 24) | (_connection->LocalCandidate().priority & 0x00FFFFFF);
        msg->AddAttribute(std::make_unique<StunUInt32Attribute>(STUN_ATTR_PRIORITY, prflx_priority));
        msg->AddMessageIntegrity(_connection->RemoteCandidate().password);
        msg->AddFingerPrint();
    }

    void ConnectionRequest::OnRequestResponse(StunMessage *msg)
    {
        _connection->OnConnectionRequestResponse(this, msg);
    }
    void ConnectionRequest::OnRequestErrorResponse(StunMessage *msg)
    {
        _connection->OnConnectionRequestErrorResponse(this, msg);
    }

    // IceConnection
    IceConnection::IceConnection(EventLoop *el, UDPPort *port, const Candidate &remote_candidate)
        : el_(el), port_(port), remote_candidate_(remote_candidate)
    {
        requests_.SignalSendPacket.connect(this, &IceConnection::OnStunSendPacket);
    }
    IceConnection::~IceConnection()
    {
    }

    // rfc5445
    // g: controlling candidate priority
    // d: controlled candidate priority
    // con prority = 2^32 * min(g,d) + 2* max(g,d) + (g>d?1:0)
    uint64_t IceConnection::Priority()
    {
        uint32_t g = LocalCandidate().priority;
        uint32_t d = RemoteCandidate().priority;
        uint64_t priority = std::min(g, d);
        priority = priority << 32;

        return priority + 2 * std::max(g, d) + (g > d ? 1 : 0);
    }

    void IceConnection::OnStunSendPacket(StunRequest *request, const char *buf, size_t len)
    {
        int ret = port_->SendTo(buf, len, remote_candidate_.address);
        if (ret < 0)
        {
            RTC_LOG(LS_WARNING) << ToString() << ": Failed to send STUN binding request: ret : " << ret
                                << ", id :" << rtc::hex_encode(request->Id());
        }
    }

    const Candidate &IceConnection::LocalCandidate() const
    {

        return port_->Candidates()[0];
    }
    int IceConnection::SendPacket(const char *data, size_t len)
    {
        if (!port_)
        {
            return -1;
        }
        return port_->SendTo(data, len, remote_candidate_.address);
    }
    void IceConnection::OnReadPacket(const char *buf, size_t len, int64_t ts)
    {

        std::unique_ptr<StunMessage> stun_msg;
        std::string remote_ufrag;
        const Candidate &remote = remote_candidate_;
        if (!port_->GetStunMessage(buf, len, remote.address, &stun_msg, &remote_ufrag))
        {
            // 如果解析失败，说明这个包不是stun 包，可能是其他的数据包，比如 dtls 或者rtp 数据包，等

            SignalReadPacket(this, buf, len, ts);
        }
        else if (!stun_msg)
        {
        }
        else
        {

            // stun message
            switch (stun_msg->Type())
            {
            case STUN_BINDING_REQUEST:
                if (remote_ufrag != remote.username)
                {
                    RTC_LOG(LS_WARNING) << ToString() << ": Received "
                                        << StunMethodToString(stun_msg->Type())
                                        << " with bad username=" << remote_ufrag
                                        << " from=" << rtc::hex_encode(stun_msg->TranscationId());
                    port_->SendBindingErrorResponse(stun_msg.get(), remote.address, STUN_ERROR_UNAUTHORIZED, STUN_ERROR_REASON_UNAUTHORIZED);
                }
                else
                {
                    RTC_LOG(LS_INFO) << ToString() << ":Received "
                                     << StunMethodToString(stun_msg->Type())
                                     << ", id = " << rtc::hex_encode(stun_msg->TranscationId());
                    HandleStunBindingRequest(stun_msg.get());
                }

                break;
            case STUN_BINDING_RESPONSE:
            case STUN_BINDING_ERROR_RESPONSE:
                stun_msg->ValidateMessageIntegrity(remote_candidate_.password);
                if (stun_msg->IntegrityOk())
                {

                    requests_.CheckResponse(stun_msg.get());
                }

                break;
            default:
                break;
            }
        }
    }
    void IceConnection::HandleStunBindingRequest(StunMessage *stun_msg)
    {
        // 解决role 冲突问题，服务器是控制者，客户端是受控者

        // 发送binging response
        SendStunBindingResponse(stun_msg);
    }
    void IceConnection::SendStunBindingResponse(StunMessage *stun_msg)
    {
        // 检测stun_msg s是否包含username 属性
        const StunByteStringAttribute *username_attr = stun_msg->GeByteString(STUN_ATTR_USERNAME);
        if (!username_attr)
        {
            RTC_LOG(LS_WARNING) << "send stun binding response error: no username";
            return;
        }
        StunMessage response;

        response.SetType(STUN_BINDING_RESPONSE);
        response.SetTransationId(stun_msg->TranscationId());
        // 4+8
        response.AddAttribute(std::make_unique<StunXorAddressAttribute>(STUN_ATTR_XOR_MAPPED_ADDRESS, RemoteCandidate().address));

        // 4+20
        response.AddMessageIntegrity(port_->IcePwd());

        // 4+4
        response.AddFingerPrint();

        SendResponseMessage(response);
    }
    void IceConnection::SendResponseMessage(const StunMessage &response)
    {
        const rtc::SocketAddress &addr = remote_candidate_.address;
        rtc::ByteBufferWriter buf;
        if (!response.Write(&buf))
        {
            return;
        }
        int ret = port_->SendTo(buf.Data(), buf.Length(), addr);
        if (ret < 0)
        {
            RTC_LOG(LS_WARNING) << ToString() << ": send " << StunMethodToString(response.Type())
                                << " error: to=" << addr.ToString()
                                << " , id=" << rtc::hex_encode(response.TranscationId());
            return;
        }
        RTC_LOG(LS_INFO) << ToString() << ": sent " << StunMethodToString(response.Type())
                         << "  to=" << addr.ToString()
                         << ", id=" << rtc::hex_encode(response.TranscationId());
        return;
    }
    std::string IceConnection::ToString()
    {

        std::stringstream ss;
        ss << "Conn[" << this << ":" << port_->TransactionName() << ":" << port_->Component() << ":" << port_->LocalAddr().ToString() << "->" << remote_candidate_.ToString();
        return ss.str();
    }

    void IceConnection::MaybeSetRemoteIceParams(const IceParameters &ice_params)
    {
        if (remote_candidate_.username == ice_params.ice_ufrag && remote_candidate_.password.empty())
        {
            remote_candidate_.password = ice_params.ice_pwd;
        }
    }

    bool IceConnection::Stable(int64_t now) const
    {

        return rtt_samples_ > RTT_RATIO + 1 && !MissResponse(now);
    }
    bool IceConnection::MissResponse(int64_t now) const
    {
        if (pings_since_last_response_.empty())
        {
            return false;
        }
        int waiting = now - pings_since_last_response_[0].sent_time;
        return waiting > 2 * rtt_;
    }
    void IceConnection::Ping(int64_t now)
    {
        last_ping_sent_ = now;
        ConnectionRequest *request = new ConnectionRequest(this);
        pings_since_last_response_.push_back(SentPing(request->Id(), now));

        RTC_LOG(LS_INFO) << ToString() << "====Sending Stun ping id: " << rtc::hex_encode(request->Id());
        requests_.Send(request);
        num_pings_sent_++;
        SetState(IceCandidatePairState::IN_PROGRESS);
    }
    void IceConnection::PrintPingsSinceLastResponse(std::string &pings, size_t max)
    {
        std::stringstream ss;
        if (pings_since_last_response_.size() > max)
        {

            for (size_t i = 0; i < max; ++i)
            {
                ss << rtc::hex_encode(pings_since_last_response_[i].id) << " ";
            }
            ss << "..." << (pings_since_last_response_.size() - max) << "more";
        }
        else
        {

            for (auto ping : pings_since_last_response_)
            {
                ss << rtc::hex_encode(ping.id) << " ";
            }
        }
        pings = ss.str();
    }

    void IceConnection::ReceivedPingResponse(int rtt)
    {

        if (rtt_samples_ > 0)
        {
            // 至少2 个 rtt
            rtt_ = rtc::GetNextMovingAverage(rtt_, rtt, RTT_RATIO);
        }
        else
        {
            rtt_ = rtt;
        }
        ++rtt_samples_;
        last_ping_response_received_ = rtc::TimeMillis();
        pings_since_last_response_.clear();
        UpdateReceiving(last_ping_response_received_);
        SetWriteState(STATE_WRITABLE);
        SetState(IceCandidatePairState::SUCCESSED);
    }
    void IceConnection::SetWriteState(WriteStateType state)
    {
        WriteStateType old_state = write_state_;
        write_state_ = state;
        if (old_state != state)
        {
            RTC_LOG(LS_INFO) << ToString() << ": set write state  from " << old_state
                             << " to " << state;
            SignalStateChange(this);
        }
    }
    bool IceConnection::TooManyPingFails(size_t max_pings, int rtt, int64_t now)
    {
        if (pings_since_last_response_.size() < max_pings)
        {
            return false;
        }
        int expected_response_time = pings_since_last_response_[max_pings - 1].sent_time + rtt;
        return now > expected_response_time;
    }
    bool IceConnection::TooLongWithoutResponse(int min_time, int64_t now)
    {

        if (pings_since_last_response_.empty())
        {
            return false;
        }
        return now > pings_since_last_response_[0].sent_time + min_time;
    }
    void IceConnection::UpdateState(int64_t now)
    {

        int rtt = 2 * rtt_;
        if (rtt < MIN_RTT)
        {
            rtt = MIN_RTT;
        }
        else if (rtt > MAX_RTT)
        {

            rtt = MAX_RTT;
        }
        if (write_state_ == STATE_WRITABLE &&
            TooManyPingFails(CONNECTION_WRITE_CONNECT_FAILS, rtt, now) &&
            TooLongWithoutResponse(CONNECTION_WRITE_CONNECT_TIMEOUT, now))
        {
            RTC_LOG(LS_INFO) << ToString() << " :Unwriable after  " << CONNECTION_WRITE_CONNECT_FAILS << "  ping fails and "
                             << now - pings_since_last_response_[0].sent_time << "  ms  without a response";
            SetWriteState(STATE_WRITE_UNRELIABLE);
        }

        if ((write_state_ == STATE_WRITE_UNRELIABLE || write_state_ == STATE_WRITE_INIT) &&
            TooLongWithoutResponse(CONNECTION_WRITE_TIMEOUT, now))
        {
            RTC_LOG(LS_INFO) << ToString() << " :Timeout after   " << CONNECTION_WRITE_TIMEOUT << "  ping fails and "
                             << now - pings_since_last_response_[0].sent_time << "  ms  without a response";
            SetWriteState(STATE_WRITE_TIMEOUT);

            // exit(0); //test
        }
        UpdateReceiving(now);
    }

    void IceConnection::UpdateReceiving(int64_t now)
    {
        bool receiving;
        if (last_ping_sent_ < last_ping_response_received_)
        {
            receiving = true;
        }
        else
        {
            receiving = LastReceived() > 0 && (now < LastReceived() + ReceivingTtimeout());
        }
        if (receiving_ == receiving)
        {
            return;
        }
        RTC_LOG(LS_INFO) << ToString() << ": set receving to " << receiving;
        receiving_ = receiving;
        SignalStateChange(this);
    }
    int64_t IceConnection::LastReceived()
    {

        return std::max(std::max(last_ping_received_, last_ping_response_received_), last_data_received_);
    }
    int IceConnection::ReceivingTtimeout()
    {
        return WEAK_CONNECTION_RECEIVE_TIMEOUT;
    }

    void IceConnection::OnConnectionRequestResponse(ConnectionRequest *request, StunMessage *msg)
    {
        int rtt = request->Elapsed();
        std::string pings;
        PrintPingsSinceLastResponse(pings, 5);
        RTC_LOG(LS_INFO) << ToString() << "Received " << StunMethodToString(msg->Type())
                         << ", id:" << rtc::hex_encode(msg->TranscationId())
                         << ", rtt" << rtt
                         << ", ping:" << pings;

        ReceivedPingResponse(rtt);
    }
    void IceConnection::OnConnectionRequestErrorResponse(ConnectionRequest *request, StunMessage *msg)
    {
        int rtt = request->Elapsed();
        int error_code = msg->GetErrorCodeValue();
        RTC_LOG(LS_WARNING) << ToString() << ": Received: "
                            << ", id=" << rtc::hex_encode(msg->TranscationId())
                            << ",rtt=" << rtt
                            << ",code=" << error_code;
        if (STUN_ERROR_UNAUTHORIZED == error_code ||
            STUN_ERROR_UNKNOWN_ATTRIBUTE == error_code ||
            STUN_ERROR_SERVER_ERROR == error_code)

        {
            // retey maybe recover
        }
        else
        {

            FailAndDestory();
        }
    }
    void IceConnection::FailAndDestory()
    {
        SetState(IceCandidatePairState::FAILED);
        Destory();
    }
    void IceConnection::Destory()
    {
        RTC_LOG(LS_INFO) << ToString() << ": Connection destoryed";
        SignalConnectionDestory(this);
        delete this;
    }
    void IceConnection::SetState(IceCandidatePairState state)
    {

        IceCandidatePairState old_state = state_;
        state_ = state;
        if (old_state != state)
        {
            RTC_LOG(LS_INFO) << ToString() << ": set state " << old_state << "->" << state_;
        }
    }

} // namespace xrtc